import {Config} from "../util/config";
import {
    PemissionRes,
    QueryPemissionRes,
    createServiceOrderReq,
    serviceOrderCompleteReq,
    postPayments
} from "../util/define";
import {Request} from "../util/request";
import {Wxapi} from "../util/wxapi";
import * as util from 'util'
import {Utils} from "../util/utils";

let intformat = require('biguint-format');
let FlakeId = require('flake-idgen');
let flakeIdGen = new FlakeId({epoch: 1300000000000});
let config = Config.getInstance();

export class Payscore {
    private mchId: string;  // 微信支付分商家号
    private serverUrl: string;   // 微信支付分服务URL https://api.mch.weixin.qq.com
    private serialNo: string;     // 微信支付分证书序列号
    private privateKey: string;     // 微信证书API密钥
    private platSerialNo: string;     // 微信分平台证书序列号
    private payCallbackUrl: string;     // 微信支付分完结订单回调地址
    private pemissionCallbackUrl: string;     // 微信支付分预授权回调地址

    constructor() {
        this.mchId = config.mchId;
        this.serverUrl = config.serverUrl;
        this.serialNo = config.serialNo;
        this.privateKey = config.privateKey;
        this.platSerialNo = config.platSerialNo;
        this.payCallbackUrl = config.pemissionCallbackUrl;
        this.pemissionCallbackUrl = config.pemissionCallbackUrl;
    }


    /**
     * TODO: 微信支付分查询平台证书
     * @returns {Promise<string>}
     */
    public async getPlatCertificates(): Promise<string> {
        let params: Map<string, string> = new Map();
        return await Request.exeGet(this.serverUrl, Wxapi.GET_CERTIFICATES, this.mchId, this.serialNo, this.privateKey, params, '');
    }


    /**
     * TODO: 微信支付分查询授权信息
     * @param {string} openId
     * @param {string} serviceId 微信支付分服务Id 由业务经理提供
     * @param {string} appId
     * @returns {Promise<QueryPemissionRes>}
     */
    public async queryPemissions(appId: string, serviceId: string, openId: string): Promise<QueryPemissionRes> {
        let params: Map<string, string> = new Map();
        params.set('appid', appId);
        params.set('service_id', serviceId);
        console.log(params);
        return await Request.exeGet(this.serverUrl, util.format(Wxapi.QUERY_PERMISSIONS_OPENID, openId), this.mchId, this.serialNo, this.privateKey, params, this.platSerialNo);
    }


    /**
     * TODO: 微信支付分预授权
     * @param {string} appId 微信小程序appId
     * @param {string} serviceId 微信支付分服务Id 由业务经理提供
     * @returns {Promise<PemissionRes>}
     */
    public async createPemissions(appId: string, serviceId: string): Promise<PemissionRes> {
        let params: Object = {
            appid: appId,
            service_id: serviceId,
            authorization_code: 'pemission' + intformat(flakeIdGen.next(), 'dec'), // 随机字符串生成授权码
            notify_url: this.pemissionCallbackUrl // 微信小程序发起支付后结果回调地址
        };
        return await Request.exePost(this.serverUrl, Wxapi.CREATE_PERMISSIONS, this.mchId, this.serialNo, this.privateKey, JSON.stringify(params), this.platSerialNo);
    }

    /**
     * TODO: 创建微信支付分订单，免确认模式
     * @param {string} appId 小程序应用id
     * @param {string} serviceId 小程序等于的服务id
     * @param {string} openId 微信小程序openId
     * @param {number} amount   1、数字，必须>0（单位分）。2、风险金额≤每个服务ID的风险金额上限。3、当商户优惠字段为空时，付费项目总金额≤服务ID的风险金额上限 （未填写金额的付费项目，视为该付费项目金额为0）。4、完结金额可大于、小于或等于风险金额。
     * @returns {Promise<string>}
     */
    public async createServiceOrder(appId: string, serviceId: string, openId: string, amount: number): Promise<string> {
        let params: createServiceOrderReq = {
            out_order_no: 'TW' + intformat(flakeIdGen.next(), 'dec'), // 商户系统内部服务订单号（不是交易单号），要求此参数只能由数字、大小写字母_-|*组成，且在同一个商户号下唯一。详见[商户订单号]。
            appid: appId, //小程序应用ID
            service_id: serviceId, // 该服务ID有本接口对应产品的权限。
            openid: openId,
            notify_url: this.payCallbackUrl, // 完结支付分订单，免密扣款后结果回调地址
            need_user_confirm: false,
            service_introduction: '智能售货柜订单', // 服务信息，用于介绍本订单所提供的服务 ，当参数长度超过20个字符时，报错处理。
            time_range: {
                start_time: Utils.Format('yyyymmddhhss')
            },
            risk_fund: { // 订单风险金信息
                name: "ESTIMATE_ORDER_COST", // 枚举值： 【先免模式】（评估不通过可交押金）可填名称为 DEPOSIT：押金ADVANCE：预付款CASH_DEPOSIT：保证金【先享模式】（评估不通过不可使用服务）可填名称为ESTIMATE_ORDER_COST：预估订单费用
                amount: amount,
                description: "智能售货柜订单分险金" // 文字，不超过30个字。
            },
        };
        return await Request.exePost(this.serverUrl, Wxapi.CREATE_SERVICE_ORDER, this.mchId, this.serialNo, this.privateKey, JSON.stringify(params), this.platSerialNo);
    }


    /**
     * TODO: 微信支付分查询订单信息
     * @param {string} appId 微信小程序appId
     * @param {string} serviceId 微信支付分服务Id 由业务经理提供
     * @param {string} out_order_no  商户系统内部服务订单号（不是交易单号），与创建订单时一致
     * @returns {Promise<string>}
     */
    public async queryServiceOrder(appId: string, serviceId: string, out_order_no: string): Promise<string> {
        let params: Map<string, string> = new Map();
        params.set('out_order_no', out_order_no); // out_order_no || query_id 二选一查询 query_id 微信侧回跳到商户前端时用于查单的单据查询id。详见章节“小程序跳转接口，回跳商户接口”。商户单号与回跳查询id必填其中一个.不允许都填写或都不填写。
        params.set('service_id', serviceId);
        params.set('appid', appId);
        console.log(params);
        return await Request.exeGet(this.serverUrl, Wxapi.QUERY_SERVICE_ORDER, this.mchId, this.serialNo, this.privateKey, params, this.platSerialNo);
    }

    /**
     *  TODO: 微信支付分完结订单
     * @param {string} appId 小程序应用id
     * @param {string} serviceId 小程序等于的服务id
     * @param {string} out_order_no 商户系统内部服务订单号（不是交易单号），要求此参数只能由数字、大小写字母_-|*组成，且在同一个商户号下唯一。详见[商户订单号]。
     * @param {number} total_amount 微信支付方订单代扣总金额
     * @param {postPayments} post_payments 微信支付方订单商品明细
     * @returns {Promise<string>}
     */
    public async serviceOrderComplete(appId: string, serviceId: string, out_order_no: string, total_amount: number, post_payments: postPayments[]): Promise<string> {
        let params: serviceOrderCompleteReq = {
            appid: appId, //小程序应用ID
            service_id: serviceId, // 该服务ID有本接口对应产品的权限。
            time_range: {
                end_time: Utils.Format('yyyymmddhhss')
            },
            total_amount: total_amount,
            post_payments: post_payments
        };
        return await Request.exePost(this.serverUrl, util.format(Wxapi.SERVICE_ORDER_COMPLETE, out_order_no), this.mchId, this.serialNo, this.privateKey, JSON.stringify(params), this.platSerialNo);
    }


    /**
     * TODO: 修改支付订单金额，只有在待支付时才能被修改成功！
     * @param {string} appId 微信小程序appId
     * @param {string} serviceId 微信支付分服务Id 由业务经理提供
     * @param {string} out_order_no  商户系统内部服务订单号（不是交易单号），与创建订单时一致
     * @param {number} total_amount 修改后订单总金额
     * @param {postPayments} post_payments
     * @param {string} reason
     * @returns {Promise<string>}
     */
    public async serviceOrderModify(appId: string, serviceId: string, out_order_no: string, total_amount: number, post_payments: postPayments, reason: string): Promise<string> {
        let params: object = {
            appid: appId, //小程序应用ID
            service_id: serviceId, // 该服务ID有本接口对应产品的权限。
            total_amount: total_amount,
            post_payments: total_amount,
            reason: reason
        };
        return await Request.exePost(this.serverUrl, util.format(Wxapi.SERVICE_ORDER_MODIFY, out_order_no), this.mchId, this.serialNo, this.privateKey, JSON.stringify(params), this.platSerialNo);
    }

    /**
     * TODO: 取消支付分订单 以下状态时可以取消订单：CREATED（已创单）、DOING（进行中）（包括商户完结支付分订单后，且支付分订单收款状态为待支付USER_PAYING)
     * @param {string} appId 微信小程序appId
     * @param {string} serviceId 微信支付分服务Id 由业务经理提供
     * @param {string} out_order_no  商户系统内部服务订单号（不是交易单号），与创建订单时一致
     * @param {string} reason 取消原因
     * @returns {Promise<string>}
     */
    public async serviceOrderCancel(appId: string, serviceId: string, out_order_no: string, reason: string): Promise<string> {
        let params: object = {
            appid: appId, //小程序应用ID
            service_id: serviceId, // 该服务ID有本接口对应产品的权限。
            reason: reason
        };
        return await Request.exePost(this.serverUrl, util.format(Wxapi.SERVICE_ORDER_CANCEL, out_order_no), this.mchId, this.serialNo, this.privateKey, JSON.stringify(params), this.platSerialNo);
    }

    /**
     * TODO: 由于收款商户进行的某些“线下操作”会导致微信支付侧的订单状态与实际情况不符。例如，用户通过线下付款的方式已经完成支付，而微信支付侧并未支付成功，此时可能导致用户重复支付。因此商户需要通过订单同步接口将订单状态同步给微信支付，修改订单在微信支付系统中的状态。
     * @param {string} appId 微信小程序appId
     * @param {string} serviceId 微信支付分服务Id 由业务经理提供
     * @param {string} out_order_no  商户系统内部服务订单号（不是交易单号），与创建订单时一致
     * @returns {Promise<string>}
     */
    public async serviceOrderSync(appId: string, serviceId: string, out_order_no: string): Promise<string> {
        let params: object = {
            appid: appId, //小程序应用ID
            service_id: serviceId, // 该服务ID有本接口对应产品的权限。
            type: 'Order_Paid', //场景类型为“Order_Paid”，字符串表示“订单收款成功” 。示例值：Order_Paid
            detail: { // 场景类型为Order_Paid时，为必填项。
                paid_time: Utils.Format('yyyymmddhhss'),  // https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter6_1_20.shtml
            }
        };
        return await Request.exePost(this.serverUrl, util.format(Wxapi.SERVICE_ORDER_SYNC, out_order_no), this.mchId, this.serialNo, this.privateKey, JSON.stringify(params), this.platSerialNo);
    }

    /**
     * TODO: 当交易发生之后一年内，由于买家或者卖家的原因需要退款时，卖家可以通过退款接口将支付金额退还给买家，微信支付将在收到退款请求并且验证成功之后，将支付款按原路退还至买家帐号上。
     * @param {string} appId 微信小程序appId
     * @param {string} serviceId 微信支付分服务Id 由业务经理提供
     * @param {string} out_order_no  商户系统内部服务订单号（不是交易单号），与创建订单时一致
     * @param {string} reason 退款原因
     * @param {number} amount 退款金额，不能大于订单总金额
     * @returns {Promise<string>}
     */
    public async serviceOrderRefund(appId: string, serviceId: string, out_order_no: string, reason: string, amount: number): Promise<string> {
        let params: object = {
            appid: appId, //小程序应用ID
            service_id: serviceId, // 该服务ID有本接口对应产品的权限。
            out_trade_no: out_order_no,
            out_refund_no: 'RW' + intformat(flakeIdGen.next(), 'dec'),
            notify_url: this.pemissionCallbackUrl,
            amount: amount
        };
        return await Request.exePost(this.serverUrl, Wxapi.SERVICE_DOMESTIC_REFUND, this.mchId, this.serialNo, this.privateKey, JSON.stringify(params), this.platSerialNo);
    }

    /**
     * TODO: 提交退款申请后，通过调用该接口查询退款状态。退款有一定延时，建议在提交退款申请后1分钟发起查询退款状态，一般来说零钱支付的退款5分钟内到账，银行卡支付的退款1-3个工作日到账。
     * @param {string} out_refund_no 商户系统内部的退款单号，商户系统内部唯一，只能是数字、大小写字母_-|*@ ，同一退款单号多次请求只退一笔。
     * @returns {Promise<string>}
     */
    public async queryServiceRefundByNo(out_refund_no: string): Promise<string> {
        let params: Map<string, string> = new Map();
        console.log(params);
        return await Request.exeGet(this.serverUrl, util.format(Wxapi.QUERY_REFUND_BY_NO, out_refund_no), this.mchId, this.serialNo, this.privateKey, params, this.platSerialNo);
    }
}
